1 /** 2 This is basically std.typecons.Nullable with extra features. 3 This module contains: 4 $(TOC isNullable) 5 $(TOC Nullable) 6 7 Copyright: Copyright the respective authors, 2008- 8 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 9 Authors: $(WEB erdani.org, Andrei Alexandrescu), 10 $(WEB bartoszmilewski.wordpress.com, Bartosz Milewski), 11 Don Clugston, 12 Shin Fujishiro, 13 Kenji Hara, 14 Matthew Armbruster 15 16 $(B Source:) $(SRC $(SRCFILENAME)) 17 */ 18 module db_constraints.utils.nullable; 19 20 import std.traits : isBuiltinType; 21 22 /** 23 Checks if you can assign the value to a $(D Nullable!T) 24 */ 25 template isNullable(T, I) 26 { 27 enum isNullable = __traits(compiles, 28 (I i) 29 { 30 Nullable!T test = i; 31 }); 32 } 33 34 /** 35 Defines a value paired with a distinctive "null" state that denotes 36 the absence of a value. If default constructed, a $(D 37 Nullable!T) object starts in the null state. Assigning it renders it 38 non-null. Calling $(D nullify) can nullify it again. 39 Practically $(D Nullable!T) stores a $(D T) and a $(D bool). 40 41 See_Also: $(LINK http://dlang.org/phobos/std_typecons.html#.Nullable) 42 43 */ 44 struct Nullable(T) 45 { 46 private T _value; 47 private bool _isNull = true; 48 49 /** 50 Constructor initializing $(D this) with $(D value). 51 Params: 52 value = The value to initialize this $(D Nullable) with 53 */ 54 this(inout T value) inout 55 { 56 _value = value; 57 _isNull = false; 58 } 59 this(N : typeof(null))(N n) inout nothrow pure @safe @nogc 60 { 61 } 62 63 template toString() 64 { 65 import std.format : FormatSpec, formatValue; 66 // Needs to be a template because of DMD @@BUG@@ 13737. 67 void toString()(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) 68 { 69 if (isNull) 70 { 71 sink.formatValue("Nullable.null", fmt); 72 } 73 else 74 { 75 sink.formatValue(_value, fmt); 76 } 77 } 78 79 // Issue 14940 80 void toString()(scope void delegate(const(char)[]) @safe sink, FormatSpec!char fmt) 81 { 82 if (isNull) 83 { 84 sink.formatValue("Nullable.null", fmt); 85 } 86 else 87 { 88 sink.formatValue(_value, fmt); 89 } 90 } 91 } 92 93 /** 94 Check if $(D this) is in the null state. 95 Returns: 96 true $(B iff) $(D this) is in the null state, otherwise false. 97 98 */ 99 @property bool isNull() const @safe pure nothrow @nogc 100 { 101 return _isNull; 102 } 103 104 /// 105 unittest 106 { 107 Nullable!int ni; 108 assert(ni.isNull); 109 110 ni = 0; 111 assert(!ni.isNull); 112 } 113 114 // Issue 14940 115 @safe unittest 116 { 117 import std.array : appender; 118 import std.format : formattedWrite; 119 120 auto app = appender!string(); 121 Nullable!int a = 1; 122 formattedWrite(app, "%s", a); 123 assert(app.data == "1"); 124 } 125 126 127 128 129 130 131 static if (!is(T == immutable(T)) && !is(T == const(T))) 132 { 133 /** 134 Assigns $(D value) to the internally-held state. If the assignment 135 succeeds, $(D this) becomes non-null. 136 Params: 137 value = A value of type $(D T) to assign to this $(D Nullable) 138 */ 139 void opAssign()(T value) 140 { 141 _value = value; 142 _isNull = false; 143 } 144 /// ditto 145 void opAssign(N : typeof(null))(N n) @safe nothrow pure @nogc 146 { 147 this.nullify(); 148 } 149 /** 150 Forces $(D this) to the null state. 151 */ 152 void nullify()() @safe nothrow pure @nogc 153 { 154 .destroy(_value); 155 _isNull = true; 156 } 157 } 158 159 /// 160 unittest 161 { 162 Nullable!int ni = 0; 163 assert(!ni.isNull); 164 165 ni.nullify(); 166 assert(ni.isNull); 167 } 168 169 170 unittest 171 { 172 //Passes 173 Nullable!(int*) npi; 174 assert(npi.isNull); 175 176 npi = null; 177 assert(npi.isNull); 178 } 179 static if (__traits(compiles, (T a, T b) { return a == b; }) && !is(T == class)) 180 { 181 bool opEquals(N : typeof(null))(N n) const nothrow pure @safe @nogc 182 { 183 return this.isNull; 184 } 185 bool opEquals(inout T rhs) const nothrow pure 186 { 187 bool result = false; 188 if (!this.isNull) 189 { 190 result = (this._value == rhs); 191 } 192 return result; 193 } 194 bool opEquals(Nullable!T rhs) const 195 { 196 bool result = false; 197 if (!rhs.isNull) 198 { 199 result = this.opEquals(rhs.get); 200 } 201 else if (!this.isNull) 202 { 203 result = false; 204 } 205 else 206 { 207 result = true; 208 } 209 return result; 210 } 211 } 212 static if (__traits(compiles, (T a, T b) { return a > b; }) && !is(T == class)) 213 { 214 int opCmp(N : typeof(null))(N n) const nothrow pure @safe @nogc 215 { 216 return (this.isNull ? 0 : 1); 217 } 218 int opCmp(inout T rhs) const nothrow pure 219 { 220 int result = -1; 221 if (!this.isNull) 222 { 223 if (this._value < rhs) 224 { 225 result = -1; 226 } 227 else if (this._value > rhs) 228 { 229 result = 1; 230 } 231 else 232 { 233 result = 0; 234 } 235 } 236 return result; 237 } 238 int opCmp(Nullable!T rhs) const nothrow pure 239 { 240 int result = 0; 241 if (!rhs.isNull) 242 { 243 result = this.opCmp(rhs.get); 244 } 245 else if (!this.isNull) 246 { 247 result = 1; 248 } 249 return result; 250 } 251 } 252 253 254 /** 255 Gets the value. $(D this) must not be in the null state. 256 This function is also called for the implicit conversion to $(D T). 257 Returns: 258 The value held internally by this $(D Nullable). 259 260 $(B Precondition:) $(D_CODE assert(!isNull);) 261 */ 262 @property ref inout(T) get() inout @safe pure nothrow 263 in 264 { 265 enum message = "Called `get' on null Nullable!" ~ T.stringof ~ "."; 266 assert(!isNull, message); 267 } 268 body 269 { 270 return _value; 271 } 272 /** 273 Gets the value or the default value passed in. 274 Returns: 275 The value held internally by this $(D Nullable) or the extra value passed in. 276 */ 277 auto ref inout(T) getValueOr(lazy inout T defVal) inout 278 { 279 return (this.isNull ? defVal : this.get); 280 } 281 282 /// 283 unittest 284 { 285 import std.exception: assertThrown, assertNotThrown; 286 287 Nullable!int ni; 288 //`get` is implicitly called. Will throw 289 //an AssertError in non-release mode 290 assertThrown!Throwable(ni += 1); 291 292 ni = 0; 293 assertNotThrown!Throwable(ni += 1); 294 } 295 296 /** 297 Implicitly converts to $(D T). 298 $(D this) must not be in the null state. 299 */ 300 alias get this; 301 } 302 303 /// 304 unittest 305 { 306 struct CustomerRecord 307 { 308 string name; 309 string address; 310 int customerNum; 311 } 312 313 Nullable!CustomerRecord getByName(string name) 314 { 315 //A bunch of hairy stuff 316 317 return Nullable!CustomerRecord.init; 318 } 319 320 auto queryResult = getByName("Doe, John"); 321 if (!queryResult.isNull) 322 { 323 //Process Mr. Doe's customer record 324 auto address = queryResult.address; 325 auto customerNum = queryResult.customerNum; 326 327 //Do some things with this customer's info 328 } 329 else 330 { 331 //Add the customer to the database 332 } 333 } 334 335 unittest 336 { 337 Nullable!int i; 338 assert(i.isNull); 339 i = 3; 340 assert(!i.isNull); 341 i = null; 342 assert(i.isNull); 343 } 344 345 unittest 346 { 347 Nullable!int i = 3; 348 assert(i.get == 3); 349 assert(i.getValueOr(4) == 3); 350 i = null; 351 assert(i.isNull && i.getValueOr(4) == 4); 352 } 353 354 unittest 355 { 356 Nullable!int i = null; 357 assert(i == null); 358 i = 3; 359 assert(i == 3); 360 Nullable!int j; 361 assert(i != j); 362 assert(i == j.getValueOr(3)); 363 int returns3(ref int count) 364 { 365 count += 1; 366 return 3; 367 } 368 int lazycalls = 0; 369 assert(i == j.getValueOr(returns3(lazycalls))); 370 assert(lazycalls == 1); 371 i = 2; 372 j = 2; 373 assert(i == j.getValueOr(returns3(lazycalls))); 374 assert(lazycalls == 1); 375 } 376 377 unittest 378 { 379 Nullable!int i; 380 Nullable!int j; 381 assert(i == j); 382 i = 5; 383 j = 6; 384 assert(i != j); 385 } 386 387 unittest 388 { 389 Nullable!int i; 390 Nullable!int j = 3; 391 assert(i < j); 392 i = 5; 393 j = 6; 394 assert(i != j); 395 } 396 397 unittest 398 { 399 import std.string; 400 struct Example 401 { 402 string name; 403 int number; 404 bool opEquals(inout(Example) ex) const pure nothrow @nogc @safe 405 { 406 return (this.name == ex.name && this.number == ex.number); 407 } 408 } 409 Nullable!Example i; 410 assert(i.isNull); 411 assert(i == null); 412 i = Example("Tom", 9); 413 assert(!i.isNull); 414 assert(i != null); 415 auto j = Example("Tom", 9); 416 assert(i == j); 417 i = null; 418 assert(i != j); 419 } 420 421 unittest 422 { 423 import std.exception : assertThrown; 424 425 Nullable!int a; 426 assert(a.isNull); 427 assertThrown!Throwable(a.get); 428 a = 5; 429 assert(!a.isNull); 430 assert(a == 5); 431 assert(a != 3); 432 assert(a.get != 3); 433 a.nullify(); 434 assert(a.isNull); 435 a = 3; 436 assert(a == 3); 437 a *= 6; 438 assert(a == 18); 439 a = a; 440 assert(a == 18); 441 a.nullify(); 442 assertThrown!Throwable(a += 2); 443 } 444 unittest 445 { 446 auto k = Nullable!int(74); 447 assert(k == 74); 448 k.nullify(); 449 assert(k.isNull); 450 } 451 unittest 452 { 453 static int f(in Nullable!int x) { 454 return x.isNull ? 42 : x.get; 455 } 456 Nullable!int a; 457 assert(f(a) == 42); 458 a = 8; 459 assert(f(a) == 8); 460 a.nullify(); 461 assert(f(a) == 42); 462 } 463 unittest 464 { 465 import std.exception : assertThrown; 466 467 static struct S { int x; } 468 Nullable!S s; 469 assert(s.isNull); 470 s = S(6); 471 assert(s == S(6)); 472 assert(s != S(0)); 473 assert(s.get != S(0)); 474 s.x = 9190; 475 assert(s.x == 9190); 476 s.nullify(); 477 assertThrown!Throwable(s.x = 9441); 478 } 479 unittest 480 { 481 // Ensure Nullable can be used in pure/nothrow/@safe environment. 482 function() @safe pure nothrow 483 { 484 Nullable!int n; 485 assert(n.isNull); 486 n = 4; 487 assert(!n.isNull); 488 assert(n == 4); 489 n.nullify(); 490 assert(n.isNull); 491 }(); 492 } 493 unittest 494 { 495 // Ensure Nullable can be used when the value is not pure/nothrow/@safe 496 static struct S 497 { 498 int x; 499 this(this) @system {} 500 } 501 502 Nullable!S s; 503 assert(s.isNull); 504 s = S(5); 505 assert(!s.isNull); 506 assert(s.x == 5); 507 s.nullify(); 508 assert(s.isNull); 509 } 510 unittest 511 { 512 // Bugzilla 9404 513 alias N = Nullable!int; 514 515 void foo(N a) 516 { 517 N b; 518 b = a; // `N b = a;` works fine 519 } 520 N n; 521 foo(n); 522 } 523 unittest 524 { 525 //Check nullable immutable is constructable 526 { 527 auto a1 = Nullable!(immutable int)(); 528 auto a2 = Nullable!(immutable int)(1); 529 auto i = a2.get; 530 } 531 //Check immutable nullable is constructable 532 { 533 auto a1 = immutable (Nullable!int)(); 534 auto a2 = immutable (Nullable!int)(1); 535 auto i = a2.get; 536 } 537 } 538 unittest 539 { 540 alias NInt = Nullable!int; 541 542 //Construct tests 543 { 544 //from other Nullable null 545 NInt a1; 546 NInt b1 = a1; 547 assert(b1.isNull); 548 549 //from other Nullable non-null 550 NInt a2 = NInt(1); 551 NInt b2 = a2; 552 assert(b2 == 1); 553 554 //Construct from similar nullable 555 auto a3 = immutable(NInt)(); 556 NInt b3 = a3; 557 assert(b3.isNull); 558 } 559 560 //Assign tests 561 { 562 //from other Nullable null 563 NInt a1; 564 NInt b1; 565 b1 = a1; 566 assert(b1.isNull); 567 568 //from other Nullable non-null 569 NInt a2 = NInt(1); 570 NInt b2; 571 b2 = a2; 572 assert(b2 == 1); 573 574 //Construct from similar nullable 575 auto a3 = immutable(NInt)(); 576 NInt b3 = a3; 577 b3 = a3; 578 assert(b3.isNull); 579 } 580 } 581 unittest 582 { 583 import std.meta : AliasSeq; 584 //Check nullable is nicelly embedable in a struct 585 static struct S1 586 { 587 Nullable!int ni; 588 } 589 static struct S2 //inspired from 9404 590 { 591 Nullable!int ni; 592 this(S2 other) 593 { 594 ni = other.ni; 595 } 596 void opAssign(S2 other) 597 { 598 ni = other.ni; 599 } 600 } 601 foreach (S; AliasSeq!(S1, S2)) 602 { 603 S a; 604 S b = a; 605 S c; 606 c = a; 607 } 608 } 609 unittest 610 { 611 // Bugzilla 10268 612 import std.json; 613 JSONValue value = null; 614 auto na = Nullable!JSONValue(value); 615 616 struct S1 { int val; } 617 struct S2 { int* val; } 618 struct S3 { immutable int* val; } 619 620 { 621 auto sm = S1(1); 622 immutable si = immutable S1(1); 623 static assert( __traits(compiles, { auto x1 = Nullable!S1(sm); })); 624 static assert( __traits(compiles, { auto x2 = immutable Nullable!S1(sm); })); 625 static assert( __traits(compiles, { auto x3 = Nullable!S1(si); })); 626 static assert( __traits(compiles, { auto x4 = immutable Nullable!S1(si); })); 627 } 628 629 auto nm = 10; 630 immutable ni = 10; 631 632 { 633 auto sm = S2(&nm); 634 immutable si = immutable S2(&ni); 635 static assert( __traits(compiles, { auto x = Nullable!S2(sm); })); 636 static assert(!__traits(compiles, { auto x = immutable Nullable!S2(sm); })); 637 static assert(!__traits(compiles, { auto x = Nullable!S2(si); })); 638 static assert( __traits(compiles, { auto x = immutable Nullable!S2(si); })); 639 } 640 641 { 642 auto sm = S3(&ni); 643 immutable si = immutable S3(&ni); 644 static assert( __traits(compiles, { auto x = Nullable!S3(sm); })); 645 static assert( __traits(compiles, { auto x = immutable Nullable!S3(sm); })); 646 static assert( __traits(compiles, { auto x = Nullable!S3(si); })); 647 static assert( __traits(compiles, { auto x = immutable Nullable!S3(si); })); 648 } 649 } 650 unittest 651 { 652 // Bugzila 10357 653 import std.datetime; 654 Nullable!SysTime time = SysTime(0); 655 } 656 unittest 657 { 658 import std.conv: to; 659 import std.array; 660 661 // Bugzilla 10915 662 Appender!string buffer; 663 664 Nullable!int ni; 665 assert(ni.to!string() == "Nullable.null"); 666 667 struct Test { string s; } 668 alias NullableTest = Nullable!Test; 669 670 NullableTest nt = Test("test"); 671 assert(nt.to!string() == `Test("test")`); 672 673 NullableTest ntn = Test("null"); 674 assert(ntn.to!string() == `Test("null")`); 675 676 class TestToString 677 { 678 double d; 679 680 this (double d) 681 { 682 this.d = d; 683 } 684 685 override string toString() 686 { 687 return d.to!string(); 688 } 689 } 690 Nullable!TestToString ntts = new TestToString(2.5); 691 assert(ntts.to!string() == "2.5"); 692 }